package cz.vse.po1715.lode.net.server;

import cz.vse.po1715.lode.net.obecne.ISHrac;
import cz.vse.po1715.lode.net.obecne.Konstanty;
import cz.vse.po1715.lode.net.obecne.KonfiguraceHry;
import cz.vse.po1715.lode.net.obecne.IHerniUdalost;
import java.nio.channels.*;
import java.util.*;
import java.net.*;
import java.io.*;
import java.util.logging.*;

/*******************************************************************************
 * Instance třídy {@code Server} představují jádro herního síťového rámce.
 * Server přijímá příchozí připojení klientů a předává je třídě
 * VyberANačti. Server také sleduje připojené hráče a správce her.
 *
 * @author    Dominik KOSTKA
 * @version   0.00.000
 */
public class Server extends Thread
{
//== KONSTANTNÍ ATRIBUTY TŘÍDY =================================================

    /** předpona používaná pro dynamické načtení správce hry */
    private static final String PŘEDPONA_SPRÁVCE_HRY =
    "cz.vse.po1715.lode.net.server.";
//== PROMĚNNÉ ATRIBUTY TŘÍDY ===================================================

    /** hráči seřazení podle Id hráče */
    private static Hashtable hráčiPodleIdHráče;

    /** hráči seřazení podle Id relace */
    private static Hashtable hráčiPodleIdRelace;
    private static long dalšíIDRelace = 0;
//== STATICKÝ INICIALIZAČNÍ BLOK - STATICKÝ KONSTRUKTOR ========================
//== KONSTANTNÍ ATRIBUTY INSTANCÍ ==============================================
//== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================

    /** Logger */
    private Logger log = Logger.getLogger("Server");

    /** serverový kanál pro přijímání klientských připojení */
    private ServerSocketChannel sSocketKanál;

    /** selector pro multiplexování serverových kanálů */
    private Selector selector;

    /** správci jednotlivých her seřazeni podle názvů her */
    private Hashtable správciHer;

    /** příznak spuštěného vlákna */
    private boolean běžící;

    /** odkaz na načítač událostí z klientských kanálů */
    private VyberANačti vyberANačti;

    /** odkaz na zapisovač událost do klientských kanálů */
    private ZapisovačUdálostí zapisovač;
//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ TŘÍDY ========================================
//== OSTATNÍ NESOUKROMÉ METODY TŘÍDY ===========================================

    /**
     * vrátí hráče se zadaným Id hráče
     */
    public static ISHrac getHracPodleId( String id) {
	return (ISHrac) hráčiPodleIdHráče.get(id);
    }

    /**
     * vrátí hráče se zadaným Id relace
     */
    public static ISHrac geHráčPodleIdRelace(String id) {
	return (ISHrac) hráčiPodleIdRelace.get(id);
    }

    /**
     * přidá hráče do našich seznamů
     */
    public static void přidejHráče(ISHrac hráč) {
	hráčiPodleIdHráče.put(hráč.getIdHrac(), hráč);
	hráčiPodleIdRelace.put(hráč.getIdRelace(), hráč);
    }

    /**
     * odstraní hráče z našich seznamů
     */
    public static void odstraňHráče(ISHrac hráč) {
	hráčiPodleIdHráče.remove(hráč.getIdHrac());
	hráčiPodleIdRelace.remove(hráč.getIdHrac());
    }


//##############################################################################
//== KONSTRUKTORY A TOVÁRNÍ METODY =============================================

    /***************************************************************************
     *konstruktor, který pouze inicializuje naše hash tabulky
     */
    public Server()
    {
        správciHer = new Hashtable();
	hráčiPodleIdHráče = new Hashtable();
  	hráčiPodleIdRelace = new Hashtable();
    }



//== ABSTRAKTNÍ METODY =========================================================
//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ INSTANCÍ =====================================
//== OSTATNÍ NESOUKROMÉ METODY INSTANCÍ ========================================

    /**
     * inicializuje server, spustí pracovní vlákna atd.
     */
    public void inicializuj() {
	log.info("Inicializace serveru");

	načtiSprávceHer();
	inicializujServerSocket();

	vyberANačti = new VyberANačti(this);
	vyberANačti.start();
//
	zapisovač = new ZapisovačUdálostí(this, Konstanty.ZAPISOVAČ_UDÁLOSTI_VLÁKNA);
    }

    /**
     * opakuje metodu select()
     * přijímá socket připojení a předává je instanci třídy VyberANačti
     */
    @Override
    public void run() {
	inicializuj();
	log.info("******** Server je spusten ********");
	běžící = true;
//	int početPřipravených = 0;

	while (běžící) {
	    // všimněte si, že od chvíle kdy máme jen jeden ServerSocket
            // k naslouchání, nepotřebujeme zde selector, ale nastavujeme ho pro
	    // pozdější dodatky jako naslouchání na jiném portu
	    // pro administrativní užití.
	    try {
		// blokující select, bude vrácen když dostaneme nové připojení
		selector.select();

		// vrací klíče
		Set připravenéKlíče = selector.selectedKeys();

		// prochází klíče a zpracovává
		Iterator i = připravenéKlíče.iterator();
		while (i.hasNext()) {
		    SelectionKey key = (SelectionKey) i.next();
		    i.remove();

		    ServerSocketChannel ssKanál = (ServerSocketChannel) key.channel();
		    SocketChannel kanálKlienta = ssKanál.accept();

		    // přidá do seznamu klientů ve vyberANačti
		    vyberANačti.přidejNovéhoKlienta(kanálKlienta);
		    log.info("obdrženo připojení od: " +
                            kanálKlienta.socket().getInetAddress());
		}
	    }
	    catch (IOException ioe) {
		log.warning("chyba během zpracování nových klientských " +
                        "připojení: " + ioe.getMessage());
	    }
	    catch (Exception e) {
		log.info("výjimka během zpracování klientskych připojení");
	    }
	}
    }

    /**
     * vypne server
     */
    public void vypni() {
	selector.wakeup();
    }

    /**
     * vrátí další dostupné Id relace
     */
    public synchronized String dalsiIDRelace() {
	return "" + dalšíIDRelace++;
    }

    /**
     * najde správce hry pro hru se zadaným názvem
     */
    public ASprávceHry getSprávceHry(String názevHry) {
	return getSprávceHryPodleHash(názevHry.hashCode());
    }

    /**
     * najde správce hry pro hru se zadaným hash kódem
     */
    public ASprávceHry getSprávceHryPodleHash(int HashKódNázvuHry) {
	ASprávceHry správceHry = (ASprávceHry) správciHer.get("" + HashKódNázvuHry);
	if (správceHry == null)
	    log.info("žádný správce pro hru s tímto hash kódem: " + HashKódNázvuHry);
	return správceHry;
    }


    /**
     * předá událost ke správě svému zapisovači událostí
     */
    public void předejUdálost(IHerniUdalost událost) {
	zapisovač.spravujUdálost(událost);
    }

    /**
     * vrátí konfiguraci hry pro hru se zadaným jménem
     */
    public KonfiguraceHry getKonfiguraceHry(String názevHry) {
	// todo: implementovat getKonfiguraceHry()
	return null;
    }

//== SOUKROMÉ A POMOCNÉ METODY TŘÍDY ===========================================
//== SOUKROMÉ A POMOCNÉ METODY INSTANCÍ ========================================

    /**
     * Server specifikuje inicializaci, propojuje s portem serveru,
     * nastavuje selector, apod.
     */
    private void inicializujServerSocket() {
	try {
	    // otevře neblokující ServerSocket kanál
	    sSocketKanál = ServerSocketChannel.open();
	    sSocketKanál.configureBlocking(false);

	    // propojí localhost na zadaném portu
	    InetAddress addr = InetAddress.getLocalHost();
	    log.info("propojení na adresu: " + addr.getHostAddress());
	    sSocketKanál.socket().bind(new InetSocketAddress(addr, Konstanty.PORT));

	    // vrací selector
	    selector = Selector.open();

	    // registruje klientský kanál se selectorem pro
            //budoucí spravu klientského kanálu
	    SelectionKey přijímacíKlíč = sSocketKanál.register(selector, SelectionKey.OP_ACCEPT);
	}
	catch (Exception e) {
	    log.info("chyba při inicializaci serverového kanálu");
	    System.exit(1);
	}
    }

    /**
     *  Načte správce her
     */
    private void načtiSprávceHer() {
	log.info("načítání správců her");
        try {
            // získá instanci správce hry a inicializuje
            ASprávceHry správceHry = new SprávceHry();
            String názevHry = správceHry.getNazevHry();
            správceHry.inicializuj(this, getKonfiguraceHry(názevHry));

            // přidá správce do naší hash tabulky správců her
            správciHer.put("" + názevHry.hashCode(), správceHry);

            log.info("načten správce pro hru: " + názevHry +
                        ", hash: " + názevHry.hashCode());
        }catch (Exception e) {
            log.info("Chyba při inicializaci správce hry");
        }
    }


//== INTERNÍ DATOVÉ TYPY =======================================================
//== TESTOVACÍ METODY A TŘÍDY ==================================================
//
//    /***************************************************************************
//     * Testovací metoda.
//     */
//    public static void test()
//    {
//        Server inst = new Server();
//    }
//    /** @param args Parametry příkazového řádku - nepoužívané. */
    public static void main( String[] args )  {
        Server server = new Server();
	server.start();  
    }
}
